package rpc

import (
	"context"
	"encoding/json"
	"time"

	"go.uber.org/zap"

	"bmember/pkg/log"

	"github.com/google/uuid"
	grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
	"google.golang.org/grpc"
	"google.golang.org/grpc/keepalive"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

// UnaryClientRequestIDInterceptor client requestid
func UnaryClientRequestIDInterceptor() grpc.UnaryClientInterceptor {
	return func(parentCtx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
		var rID string
		rIDi := parentCtx.Value("_requestID")
		if rIDi != nil {
			rID = rIDi.(string)
		} else {
			uid, _ := uuid.NewUUID()
			rID = uid.String()
		}
		childCtx := metadata.AppendToOutgoingContext(parentCtx, "x-request-id", rID)
		return invoker(childCtx, method, req, reply, cc, opts...)
	}
}

// UnaryClientLoggerInterceptor client caller logger
func UnaryClientLoggerInterceptor(logger log.Factory) grpc.UnaryClientInterceptor {
	return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
		start := time.Now()
		// 日志整理
		err := invoker(ctx, method, req, reply, cc, opts...)
		du := time.Since(start)
		s, _ := status.FromError(err)
		reqB, _ := json.Marshal(req)
		if len(reqB) > 2048 {
			reqB = reqB[:2048]
		}
		var respB []byte
		if err != nil {
			respB = []byte(err.Error())
		} else {
			respB, _ = json.Marshal(reply)
			if len(respB) > 2048 {
				respB = respB[:2048]
			}
		}
		fields := make([]zap.Field, 5)
		fields[0] = zap.Int32("rpc_code", int32(s.Code()))
		fields[1] = zap.String("rpc_method", method)
		fields[2] = zap.ByteString("request_body", reqB)
		fields[3] = zap.String("duration", du.String())
		fields[4] = zap.ByteString("response", respB)
		if s.Code() != 0 && s.Code() < 1000 {
			logger.For(ctx).Error("rpc caller failed", fields...)
		} else {
			logger.For(ctx).Info("rpc caller success", fields...)
		}
		return err
	}
}

// NewGrpcConn 新建 rpc client
func NewGrpcConn(address string, logger log.Factory) (*grpc.ClientConn, error) {
	return grpc.Dial(address, grpc.WithInsecure(),
		grpc.WithChainUnaryInterceptor(
			grpc_retry.UnaryClientInterceptor(),
			UnaryClientRequestIDInterceptor(),
			UnaryClientLoggerInterceptor(logger),
		),
		grpc.WithKeepaliveParams(keepalive.ClientParameters{
			Time:                10 * time.Second,
			Timeout:             2 * time.Second,
			PermitWithoutStream: true,
		}),
	)
}
